package cn.com.bluemoon.daps.model.service.impl;

import cn.com.bluemoon.daps.common.domain.ResultBean;
import cn.com.bluemoon.daps.common.enums.DatabaseType;
import cn.com.bluemoon.daps.common.exception.DapException;
import cn.com.bluemoon.daps.common.exception.DapThrowException;
import cn.com.bluemoon.daps.common.toolkit.WebDownloadUtils;
import cn.com.bluemoon.daps.domp.api.FastDFSService;
import cn.com.bluemoon.daps.model.core.file.*;
import cn.com.bluemoon.daps.model.core.file.export.ExportStrategy;
import cn.com.bluemoon.daps.model.core.file.export.FileExport;
import cn.com.bluemoon.daps.model.core.file.export.FileExportFactory;
import cn.com.bluemoon.daps.model.core.file.parser.FileType;
import cn.com.bluemoon.daps.model.core.upload.DapModelExeclExport;
import cn.com.bluemoon.daps.model.entity.*;
import cn.com.bluemoon.daps.model.service.DapDataModelExportService;
import cn.com.bluemoon.daps.model.service.DapDataModelService;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuple4;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 模型导chu功能
 *
 * @author Jarod.Kong
 */
@Slf4j
@Service
public class DapDataModelExportServiceImpl implements DapDataModelExportService {
    @Resource
    private DapDataModelService modelService;

    @Autowired
    private FastDFSService fastDFSService;

    @Autowired
    private DapModelExeclExport dapModelExeclExport;

    /**
     * 导出
     *
     * @param modelId 模型id
     * @return 结果
     */
    @Override
    public ResultBean<?> exportExcelByModelId(HttpServletResponse response, String modelId) {
        return exportExcelByModelIdAndExportStrategy(ExportStrategy.Excel_OutputStream, response, modelId);
    }


    /**
     * 利用fastDfs存储文件
     *
     * @param modelId 模型id
     * @return 结果
     */
    @Override
    public ResultBean<?> exportExcelByModelId(String modelId) {
        return exportExcelByModelIdAndExportStrategy(ExportStrategy.FastDFS, null, modelId);
    }


    @Override
    public void exportModelV2ByModelId(HttpServletResponse response, String modelId, String groupId) {
        Tuple2<DapDataModel, Optional<DatabaseType>> dbTypeOpt = modelService.findModelAndDbTypeByModelId(modelId);
        DatabaseType dbType = dbTypeOpt.getT2().orElseThrow(() -> new DapThrowException("模型对应的数据源非数据库类型，可能为文件系统数据源!!"));
        log.info("模型id={}导出文件数据库类型{}", modelId, dbType);
        if (!DatabaseType.PGSQL.equals(dbType) && !DatabaseType.POSTGRESQL.equals(dbType)) {
            throw new DapException("该导入功能只支持数据库类型为PG的模型！");
        }
        // 模板加工
        String tempFilePath = null;
        try {
            tempFilePath = dapModelExeclExport.exportExcel(modelId, groupId);
            WebDownloadUtils.commonDownload(response, tempFilePath, "PG模型导出.xlsx");
        } catch (Exception e) {
            log.error("下载异常", e);
            throw new DapException("下载模板异常", e);
        } finally {
            if (tempFilePath != null) {
                File file = new File(tempFilePath);
                if (file.exists()) {
                    file.delete();
                }
            }
        }
    }

    /**
     * 导出
     * 其中{@link ExportStrategy#Excel_OutputStream}的方式，在数据量大时候非常慢，建议使用{@link ExportStrategy#FastDFS}
     *
     * @param exportStrategy 方式
     * @param response       流
     * @param modelId        模型
     * @return 结果
     */
    private ResultBean<?> exportExcelByModelIdAndExportStrategy(ExportStrategy exportStrategy, HttpServletResponse response, String modelId) {
        final FileType exportFileType = FileType.EXCEL;
        if (exportStrategy == null) {
            exportStrategy = ExportStrategy.FastDFS;
        }

        Tuple2<DapDataModel, Optional<DatabaseType>> modelOpt = modelService.findModelAndDbTypeByModelId(modelId);
        DatabaseType dbType = modelOpt.getT2().orElseThrow(() -> new DapException("可能为文件系统类数据源，不可导出数据库类型的文件"));
        log.info("数据库类型{}", dbType);
        // 根据modelId查询表信息、表字段信息、表索引信息、表分区信息
        // 目前提供开关拉取模型的最新历史发布数据还是最新数据 true 为最新，false为最新已发布
        StopWatch sw = new StopWatch();
        sw.start("获取模型表信息、表字段信息、表索引信息、表分区信息");
        boolean newest = true;
        Tuple4<List<DapDataModelTable>, List<DapDataModelTableField>, List<DapDataModelSubordinateKey>,
                List<DapDataModelTablePartition>> tuple4 = modelService.queryModelInfosByModelAndNewest(modelId, newest);
        sw.stop();
        String fileName = System.getProperty("user.dir") + File.separator + "系统外模型导出_数仓模型_" + dbType + "_" + System.currentTimeMillis() + ".xlsx";
        final File file = new File(fileName);
        try {
            Files.createFile(Paths.get(fileName));
            sw.start("组装模型表信息");
            ExcelWriter excelWriter = EasyExcel.write(fileName).build();
            try {
                List<DapDataModelTable> tables = tuple4.getT1().stream().sorted(new TableComparator()).collect(Collectors.toList());
                List<DapDataModelTableField> fields = tuple4.getT2().stream().sorted(new TableFComparator().thenComparing(new TableFSortComparator())).collect(Collectors.toList());
                List<DapDataModelSubordinateKey> indexes = tuple4.getT3().stream().sorted(new TableIComparator()).collect(Collectors.toList());
                List<DapDataModelTablePartition> partitions = tuple4.getT4().stream().sorted(new TablePComparator()).collect(Collectors.toList());
                // 后续可以使用策略进行组装个类数据
                writeTableToExcelSheet(modelId, dbType, excelWriter, tables);
                sw.stop();
                sw.start("组装模型字段信息");
                writeFieldToExcelSheet(modelId, dbType, excelWriter, fields);
                sw.stop();
                sw.start("组装模型索引信息");
                writeIndexToExcelSheet(modelId, dbType, excelWriter, indexes, fields);
                sw.stop();
                sw.start("组装模型分区信息");
                writePartitionToExcelSheet(modelId, dbType, excelWriter, partitions);
                sw.stop();
            } finally {
                // 千万别忘记finish 会帮忙关闭流
                if (!sw.isRunning()) {
                    sw.start("执行所有模型数据");
                }
                if (excelWriter != null) {
                    excelWriter.finish();
                    log.info("导出Excel的模型{}数据库{}的数据完成", modelId, dbType);
                }
                sw.stop();
            }
            sw.start("导出文件");
            FileExport fileExport = FileExport.builder().file(file).fileName(file.getName())
                    .fileType(exportFileType).httpServletResponse(response).build();
            ResultBean<?> exportR = FileExportFactory.fileExport(exportStrategy, fileExport, fastDFSService);
            return exportR;
        } catch (Exception e) {
            log.error("导入异常", e);
            return ResultBean.error("系统导入文件异常");
        } finally {
            boolean delete = file.delete();
            log.info("删除临时文件{}，情况{}", file.getAbsolutePath(), delete);
            sw.stop();
            log.info("输出报告结果：{}{}", System.lineSeparator(), sw.prettyPrint());
        }
    }

    @SuppressWarnings({"rawtypes"})
    private void writePartitionToExcelSheet(String modelId, DatabaseType dbType, ExcelWriter excelWriter, List<DapDataModelTablePartition> partitions) {
        if (DatabaseType.PGSQL.equals(dbType) || DatabaseType.MYSQL.equals(dbType)) {
            log.info("{}跳过导出{}", dbType, "分区");
            return;
        }
        IMetaData pm = MetadataFatory.use0(dbType, MetadataFatory.Type.PARTITION);
        AtomicInteger order = new AtomicInteger(1);
        List<IMetaData> fdata = partitions.stream().map(d -> {
            PartitionData data = (PartitionData) MetadataFatory.use0(dbType, MetadataFatory.Type.PARTITION);
            return data.copyDbProperties0(d);
        }).filter(Objects::nonNull).peek(d -> d.setOrder(order.getAndIncrement())).collect(Collectors.toList());
        excelWriter.write(fdata, EasyExcel.writerSheet(pm.sheetNo(), pm.sheetName()).head(pm.getClass()).build());
        log.info("注入导出Excel的模型{}数据库{}的【分区】信息条数{}", modelId, dbType, partitions.size());
    }

    @SuppressWarnings({"rawtypes"})
    private void writeIndexToExcelSheet(String modelId, DatabaseType dbType, ExcelWriter excelWriter, List<DapDataModelSubordinateKey> indexes, List<DapDataModelTableField> fields) {
        if (DatabaseType.HIVE.equals(dbType)) {
            log.info("{}跳过导出{}", dbType, "索引");
            return;
        }
        IMetaData im = MetadataFatory.use0(dbType, MetadataFatory.Type.INDEX);
        AtomicInteger order = new AtomicInteger(1);
        Map<String, Map<String, String>> tableIdAndFieldIdAndNameMap = fields.stream().collect(Collectors.groupingBy(DapDataModelTableField::getModelTableId,
                Collectors.toMap(DapDataModelTableField::getId, DapDataModelTableField::getFieldName, (a, b) -> b)));
        List<IMetaData> fdata = indexes.stream().map(d -> {
            IndexData indexData = (IndexData) MetadataFatory.use0(dbType, MetadataFatory.Type.INDEX);
            indexData.setTableIdAndFieldIdAndNameMap(tableIdAndFieldIdAndNameMap);
            return indexData.copyDbProperties0(d);
        }).filter(Objects::nonNull).peek(d -> d.setOrder(order.getAndIncrement())).collect(Collectors.toList());
        excelWriter.write(fdata, EasyExcel.writerSheet(im.sheetNo(), im.sheetName()).head(im.getClass()).build());
        log.info("注入导出Excel的模型{}数据库{}的【索引】信息条数{}", modelId, dbType, indexes.size());
    }

    @SuppressWarnings({"rawtypes"})
    private void writeFieldToExcelSheet(String modelId, DatabaseType dbType, ExcelWriter excelWriter, List<DapDataModelTableField> fields) {
        IMetaData fm = MetadataFatory.use0(dbType, MetadataFatory.Type.FIELD);
        AtomicInteger order = new AtomicInteger(1);
        List<IMetaData> fdata = fields.stream().map(d -> {
            FieldData fieldData = (FieldData) MetadataFatory.use0(dbType, MetadataFatory.Type.FIELD);
            return fieldData.copyDbProperties0(d);
        }).filter(Objects::nonNull).peek(f -> f.setOrder(order.getAndIncrement())).collect(Collectors.toList());
        excelWriter.write(fdata, EasyExcel.writerSheet(fm.sheetNo(), fm.sheetName()).head(fm.getClass()).build());
        log.info("注入导出Excel的模型{}数据库{}的【字段】信息条数{}", modelId, dbType, fields.size());
    }

    @SuppressWarnings({"rawtypes"})
    private void writeTableToExcelSheet(String modelId, DatabaseType dbType, ExcelWriter excelWriter, List<DapDataModelTable> tables) {
        IMetaData metadata = MetadataFatory.use0(dbType, MetadataFatory.Type.TABLE);
        AtomicInteger order = new AtomicInteger(1);
        List<IMetaData> tdata = tables.stream().map(d -> {
            TableData data = (TableData) MetadataFatory.use0(dbType, MetadataFatory.Type.TABLE);
            return data.copyDbProperties0(d);
        }).filter(Objects::nonNull).peek(d -> d.setOrder(order.getAndIncrement())).collect(Collectors.toList());
        excelWriter.write(tdata, EasyExcel.writerSheet(metadata.sheetNo(), metadata.sheetName()).head(metadata.getClass()).build());
        log.info("注入导出Excel的模型{}数据库{}的【表】信息条数{}", modelId, dbType, tables.size());
    }

    static class TableComparator implements Comparator<DapDataModelTable> {

        @Override
        public int compare(DapDataModelTable o1, DapDataModelTable o2) {
            if (o1 == null || o1.getEngName() == null) {
                return 1;
            }
            if (o2 == null || o2.getEngName() == null) {
                return 1;
            }
            return o1.getEngName().compareTo(o2.getEngName());
        }
    }

    static class TableFSortComparator implements Comparator<DapDataModelTableField> {

        @Override
        public int compare(DapDataModelTableField o1, DapDataModelTableField o2) {
            if (o1 == null || o1.getTableName() == null || o1.getFieldRank() == null) {
                return 1;
            }
            if (o2 == null || o2.getTableName() == null || o2.getFieldRank() == null) {
                return 1;
            }
            return o1.getFieldRank().compareTo(o2.getFieldRank());
        }
    }

    static class TableFComparator implements Comparator<DapDataModelTableField> {

        @Override
        public int compare(DapDataModelTableField o1, DapDataModelTableField o2) {
            if (o1 == null || o1.getTableName() == null) {
                return 1;
            }
            if (o2 == null || o2.getTableName() == null) {
                return 1;
            }
            return o1.getTableName().compareTo(o2.getTableName());
        }
    }

    static class TableIComparator implements Comparator<DapDataModelSubordinateKey> {

        @Override
        public int compare(DapDataModelSubordinateKey o1, DapDataModelSubordinateKey o2) {
            if (o1 == null || o1.getTableName() == null) {
                return 1;
            }
            if (o2 == null || o2.getTableName() == null) {
                return 1;
            }
            return o1.getTableName().compareTo(o2.getTableName());
        }
    }

    static class TablePComparator implements Comparator<DapDataModelTablePartition> {

        @Override
        public int compare(DapDataModelTablePartition o1, DapDataModelTablePartition o2) {
            if (o1 == null || o1.getTableName() == null) {
                return 1;
            }
            if (o2 == null || o2.getTableName() == null) {
                return 1;
            }
            return o1.getTableName().compareTo(o2.getTableName());
        }
    }

}
